package com.zelin.gulimall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.zelin.gulimall.product.entity.CategoryBrandRelationEntity;
import com.zelin.gulimall.product.service.CategoryBrandRelationService;
import com.zelin.gulimall.product.vo.Catelog2Vo;

import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zelin.common.utils.PageUtils;
import com.zelin.common.utils.Query;

import com.zelin.gulimall.product.dao.CategoryDao;
import com.zelin.gulimall.product.entity.CategoryEntity;
import com.zelin.gulimall.product.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {
    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(new Query<CategoryEntity>().getPage(params), new QueryWrapper<CategoryEntity>());

        return new PageUtils(page);
    }

    //1.查询所有菜单及其子菜单列表
    @Override
    public List<CategoryEntity> listWithTree() {
        //1.1)查询所有分类列表
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
        //System.out.println("categoryEntities = " + categoryEntities);

        //1.2)查询子菜单列表
        // List<CategoryEntity> entityList =
        //         categoryEntities
        //         .stream()
        //         .filter(categoryEntity -> categoryEntity.getParentCid() == 0)
        //         .map(menu->{
        //             menu.setChildren(getChildrenMenu(menu,categoryEntities));
        //             return menu;
        //         }).sorted((menu1, menu2) -> {
        //             return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        //         }).collect(Collectors.toList());
        //1.2)先找出一级节点
        //1.2.1)定义存放一级节点的集合
        List<CategoryEntity> root = new ArrayList<>();
        for (CategoryEntity categoryEntity : categoryEntities) {
            if (categoryEntity.getParentCid() == 0) {
                root.add(categoryEntity);
            }
        }

        this.findChildrenByParentId(root, categoryEntities);

        return root;
    }

    private void findChildrenByParentId(List<CategoryEntity> root, List<CategoryEntity> categoryEntities) {
        for (CategoryEntity parent : root) {
            //构建子节点集合
            List<CategoryEntity> children = new ArrayList<>();
            for (CategoryEntity child : categoryEntities) {
                if (parent.getCatId() == child.getParentCid()) {
                    //添加子节点
                    children.add(child);
                    //将子节点集合与父节点关联
                    parent.setChildren(children);
                    //递归
                    findChildrenByParentId(parent.getChildren(), categoryEntities);
                }
            }

        }
    }
    //根据分类id查询此分类的所有父id并放到数组中
    @Override
    public Long[] findParentIdByCategoryId(Long catelogId) {
        LinkedList<Long> categoryIds = new LinkedList<>(); findByParentId(categoryIds, catelogId);
        return categoryIds.toArray(new Long[categoryIds.size()]);
    }

    //级联更新分类及中间表，修改某个分类时，清空缓存
    @Override
    //处理方法一：@CacheEvict(value = "category",allEntries = true)  //代表清空category分区下的所有缓存（方法一：处理缓存与数据库不一致【@CacheEvict注解：失效性方法，@CachePut双写模式，需要方法有返回值】）
    @CacheEvict(value = "category",allEntries = true)
    //处理方法二：使用下面的注解，将要删除的缓存放在evict关联的数组中
    // @Caching(evict = {
    //         @CacheEvict(value = "category",key = "'getLevel1Categorys'"),
    //         @CacheEvict(value = "category",key = "'getCategoryJson'")
    // })

    @Transactional
    public void updateDetail(CategoryEntity category) {
        updateById(category);

        CategoryBrandRelationEntity entity = new CategoryBrandRelationEntity();
        entity.setCatelogId(category.getCatId()); entity.setCatelogName(category.getName());
        UpdateWrapper<CategoryBrandRelationEntity> wrapper = new UpdateWrapper<>();
        wrapper.eq("catelog_id", category.getCatId());

        categoryBrandRelationService.update(entity, wrapper);

    }

    /**
     * 查询一级分类
     *
     * @return
     */
    @Cacheable(value = {"category"},key = "#root.method.name") //方法名作为key，value代表缓存分区（就是可以每个模块使用各自的缓存，互不干涉）
    public List<CategoryEntity> getLevel1Categorys() {
        System.out.println("正在从数据库查询数据。。。");
        List<CategoryEntity> categoryEntities = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
        return categoryEntities;
    }

    //查询所有的分类列表
    private List<CategoryEntity> findAll() {
        List<CategoryEntity> categoryEntities = this.baseMapper.selectList(null);
        return categoryEntities;
    }
    //将二、三级分类通过@Cachable放到缓存中
    @Cacheable(value = {"category"},key = "#root.methodName")
    public Map<String, List<Catelog2Vo>> getCategoryJson() {
        //0.查询所有的分类列表(目的是只查询一次数据库,提高效率)
        List<CategoryEntity> all = findAll();
        //System.out.println("all = " + all);
        //1.查询一级分类
        //List<CategoryEntity> level1Categorys = getLevel1Categorys();
        List<CategoryEntity> level1Categorys = all.stream().filter(m -> m.getParentCid().longValue() == 0).collect(Collectors.toList());
        System.out.println("level1Categorys = " + level1Categorys);
        //2.查询二级分类及其三级分类
        Map<String, List<Catelog2Vo>> catelog2Vos = level1Categorys.stream().collect(Collectors.toMap(k1 -> k1.getCatId().toString(), v1 -> {
            //2.1)查询二 级分类(一级分类的id等于二级分类的父id)
            //List<CategoryEntity> level2Categorys = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v1.getCatId()));
            List<CategoryEntity> level2Categorys = all.stream().filter(f -> v1.getCatId().longValue() == f.getParentCid().longValue()).collect(Collectors.toList());
            //System.out.println("level2Categorys = " + level2Categorys);
            List<Catelog2Vo> catelog2VosList = level2Categorys.stream().map(m -> {
                //2.2)查询三级分类
                //List<CategoryEntity> level3Categorys = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", m.getCatId()));
                List<CategoryEntity> level3Categorys = all.stream().filter(f -> m.getCatId().longValue() == f.getParentCid().longValue()).collect(Collectors.toList());
                //System.out.println("level3Categorys = " + level3Categorys);
                //2.3)收集三级分类的Catelog3Vo集合
                List<Catelog2Vo.Catalog3Vo> catalog3List = level3Categorys.stream().map(m1 -> {
                    Catelog2Vo.Catalog3Vo catalog3Vo = new Catelog2Vo.Catalog3Vo(m.getCatId().toString(), m1.getCatId().toString(), m1.getName());
                    return catalog3Vo;
                }).collect(Collectors.toList());
                Catelog2Vo catelog2Vo = new Catelog2Vo(v1.getCatId().toString(), catalog3List, m.getCatId().toString(), m.getName());
                return catelog2Vo;
            }).collect(Collectors.toList());
            return catelog2VosList;

        }));
        return catelog2Vos;
    }
    //将数据放到redis中
    public Map<String, List<Catelog2Vo>> getCategoryJson2() {
        //1.从redis中获取菜单数据
        String categoryJson = redisTemplate.opsForValue().get("categoryJson");
        //2.判断是否有值
        if (StringUtils.isBlank(categoryJson)) {
            //2.1）从数据库查询数据
            Map<String, List<Catelog2Vo>> catetgoryEntities = getCategoriesDbByRedissionLock();
            //2.2)将数据放到redis中
            redisTemplate.opsForValue().set("categoryJson", JSON.toJSONString(catetgoryEntities), 1, TimeUnit.DAYS);
            //2.3)返回
            return catetgoryEntities;
        }
        //3.如果存在数据就直接转换并返回集合数据
        Map<String, List<Catelog2Vo>> listMap = JSON.parseObject(categoryJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
        });
        return listMap;
    }
    public Map<String, List<Catelog2Vo>> getCategoriesDbByRedissionLock() {
        //加锁
        RLock myLock = redissonClient.getLock("myLock");
        myLock.lock(20,TimeUnit.SECONDS);
        try {
            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();
            return dataFromDb;
        }finally {
            myLock.unlock();
        }
    }
    public Map<String, List<Catelog2Vo>> getCategoriesDbByRedisLock() {
        String uuid = UUID.randomUUID().toString();
        //redis锁的关键：加锁保证原子性，解锁也要保证原子性
        //1.如果不存在lock这个锁，就设置进去，此方法相当于redis的setNX（）方法，可以查看源码
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111", 300, TimeUnit.SECONDS);
        if (lock) {       //如果加锁成功，就可以获取数据
            //2.设置过期时间，解决死锁问题，如果此时死机，还是有问题，解决办法是：将过期时间与设置锁的key同时进行
            //redisTemplate.expire("lock",30,TimeUnit.SECONDS);   //30s自动过期，即便出出了问题也不会死锁
            System.out.println("获取分布式锁成功！");
            //最终做法
            Map<String, List<Catelog2Vo>> dataFromDb;
            try {
                //加锁成功，执行业务
                dataFromDb = getDataFromDb();  //在执行业务中出现异常或程序执行过程中死机，就会出现死锁。
            } finally {
                //解锁方式一：完成后，获取值+对比成功删除=原子操作，就可以释放锁了
                // String lockValue = redisTemplate.opsForValue().get("lock");
                // if(uuid.equals(lockValue)){
                //     redisTemplate.delete("lock");
                // }
                //解锁方式二：lua脚本解锁,保证了解锁的原子性(参看redis的官网：http://www.redis.cn/commands/set.html)
                String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
                Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
            }
            return dataFromDb;
        } else {
            //这里可以休眠100ms，避免重试的频率太高
            System.out.println("获取分布式锁失败，等待重试！");
            return getCategoriesDbByRedisLock();    //自旋的方式，如果没有加到锁就重试
        }
    }

    private Map<String, List<Catelog2Vo>> getDataFromDb() {
        //首先，从redis中查询数据，如果有就直接返回，否则从数据库中查询
        String categoryJson = redisTemplate.opsForValue().get("categoryJson");
        if (StringUtils.isNotBlank(categoryJson)) {
            Map<String, List<Catelog2Vo>> categorys = JSON.parseObject(categoryJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {
            });
            return categorys;
        }


        //0.查询所有的分类列表(目的是只查询一次数据库,提高效率)
        List<CategoryEntity> all = findAll();
        //System.out.println("all = " + all);
        //1.查询一级分类
        //List<CategoryEntity> level1Categorys = getLevel1Categorys();
        List<CategoryEntity> level1Categorys = all.stream().filter(m -> m.getParentCid().longValue() == 0).collect(Collectors.toList());
        System.out.println("level1Categorys = " + level1Categorys);
        //2.查询二级分类及其三级分类
        Map<String, List<Catelog2Vo>> catelog2Vos = level1Categorys.stream().collect(Collectors.toMap(k1 -> k1.getCatId().toString(), v1 -> {
            //2.1)查询二 级分类(一级分类的id等于二级分类的父id)
            //List<CategoryEntity> level2Categorys = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", v1.getCatId()));
            List<CategoryEntity> level2Categorys = all.stream().filter(f -> v1.getCatId().longValue() == f.getParentCid().longValue()).collect(Collectors.toList());
            //System.out.println("level2Categorys = " + level2Categorys);
            List<Catelog2Vo> catelog2VosList = level2Categorys.stream().map(m -> {
                //2.2)查询三级分类
                //List<CategoryEntity> level3Categorys = this.baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", m.getCatId()));
                List<CategoryEntity> level3Categorys = all.stream().filter(f -> m.getCatId().longValue() == f.getParentCid().longValue()).collect(Collectors.toList());
                //System.out.println("level3Categorys = " + level3Categorys);
                //2.3)收集三级分类的Catelog3Vo集合
                List<Catelog2Vo.Catalog3Vo> catalog3List = level3Categorys.stream().map(m1 -> {
                    Catelog2Vo.Catalog3Vo catalog3Vo = new Catelog2Vo.Catalog3Vo(m.getCatId().toString(), m1.getCatId().toString(), m1.getName());
                    return catalog3Vo;
                }).collect(Collectors.toList());
                Catelog2Vo catelog2Vo = new Catelog2Vo(v1.getCatId().toString(), catalog3List, m.getCatId().toString(), m.getName());
                return catelog2Vo;
            }).collect(Collectors.toList());
            return catelog2VosList;

        }));
        return catelog2Vos;
    }

    //查询二级、三级分类（从数据库中获取）
    public Map<String, List<Catelog2Vo>> getCategoriesDb() {
        //首先，从redis中查询数据，如果有就直接返回，否则从数据库中查询
        return getDataFromDb();
    }

    private LinkedList<Long> findByParentId(LinkedList<Long> categoryIds, Long catelogId) {
        categoryIds.addFirst(catelogId); CategoryEntity entity = this.getById(catelogId);
        if (entity.getParentCid() != 0) {
            findByParentId(categoryIds, entity.getParentCid());
        } return categoryIds;
    }

    //逻辑删除子菜单
    @Override
    public void removeMenuByIds(List<Long> ids) {
        //TODO:检查当前菜单是否被别的地方引用
        baseMapper.deleteBatchIds(ids);
    }

    //2.获取子菜单列表
    private List<CategoryEntity> getChildrenMenu(CategoryEntity entity, List<CategoryEntity> categoryEntities) {
        List<CategoryEntity> menus = categoryEntities.stream()
                .filter(categoryEntity ->
                        categoryEntity.getParentCid() == entity.getCatId()
                )
                .map(menu -> {
                    menu.setChildren(getChildrenMenu(menu, categoryEntities));
                    return menu;
                }).sorted((menu1, menu2) -> {
                    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
                }).collect(Collectors.toList());
        return menus;
    }

}